Skip to content

feat(ai): auto-growing composer + graceful image attachments - #11

Merged
oreofeolurin merged 4 commits into
mainfrom
feat/ai-composer-textarea-attachments
Jun 11, 2026
Merged

feat(ai): auto-growing composer + graceful image attachments#11
oreofeolurin merged 4 commits into
mainfrom
feat/ai-composer-textarea-attachments

Conversation

@oreofeolurin

Copy link
Copy Markdown
Contributor

What

Reworks the Ask AI composer per feedback: the input now grows as you type, sits taller by default, and (when the model supports it) accepts image attachments.

Composer

  • Single-line <input> → auto-growing <textarea>: taller default (min 46px), grows with content up to 168px, then scrolls.
  • Enter sends · Shift+Enter newline.
  • Paperclip attachment — shown only when the model is vision-capable (graceful: never misleads when it wouldn't work). Up to 4 images, 5 MB each. Thumbnails appear as removable chips and render in the sent message bubble.
  • Image data is stripped before persisting chat history, so attachments never blow the localStorage quota (conversation text still survives reload).

Vision gating

aiConfig() now exposes vision, inferred from the model id (modelSupportsVision heuristic: gpt-4o, claude-3+, gemini, llava, pixtral, *-vl, …) and overridable with an explicit ai.vision: true|false in markline.json.

Note: the default sample config (deepseek/deepseek-v4-flash) is not multimodal, so the paperclip is hidden until an operator sets vision: true or switches to a vision model.

Plumbing

  • transport.buildMessages() accepts images and emits OpenAI-style multimodal content parts.
  • /api/ai proxy accepts and forwards up to 4 data:image/ URLs.

Also folded in (earlier copy fixes)

  • Ask questions about this page (was 'this API … your integration')
  • Generic, page-relevant starter suggestions
  • Neutral context chip (no hardcoded 'API Reference —')
  • Shorter disclaimer: 'Answers are AI-generated and may contain mistakes'

Testing

  • tsc --noEmit clean; next lint clean (one pre-existing messages useMemo warning, unrelated).
  • Visual check pending on a vision-enabled config.

Composer (ask-dock):
- Replace the single-line <input> with an auto-growing <textarea>: taller
  default (min 46px), grows with content up to 168px, then scrolls.
- Enter sends, Shift+Enter inserts a newline.
- Add an image attachment affordance (paperclip) shown ONLY when the model
  supports vision (modelSupportsVision heuristic or explicit ai.vision flag).
  Up to 4 images, 5MB each; thumbnails as removable chips, rendered in the
  user bubble. Image data is stripped before persisting chat history so we
  never blow the localStorage quota.

Plumbing:
- transport.buildMessages() now accepts images and emits OpenAI-style
  multimodal content parts.
- /api/ai proxy accepts and forwards up to 4 data:image/ URLs.
- aiConfig() exposes vision (inferred from model id, overridable via ai.vision).

Also folds in the earlier copy fixes: 'Ask questions about this page',
generic page-relevant suggestions, neutral context chip (no hardcoded
'API Reference'), and a shorter disclaimer.
Titles: derive the chat title from the first question with summarizeTitle()
— strip filler, capitalize, cut on a word boundary (no more mid-word
'beha…'), drop trailing punctuation. Deterministic, so it works even when
the assistant is unavailable.

Errors: never surface raw provider errors to readers. The proxy route logs
the upstream detail server-side and returns a generic message (preserving
429 for rate limits); the client maps errors to safe, actionable text via
friendlyError() and logs the raw detail to the console. Removes the old
behavior that echoed the provider's 404 JSON (incl. openrouter.ai privacy
URL) straight into the chat.
Before: the model received only a section-name string ('you are viewing the
"Releases" section') and pickSources() returned hardcoded fake labels — so it
had no actual content and replied 'the provided context does not cover this',
even on the very page that answered the question.

Now lib/ai/retrieval.ts grounds every answer in real docs, client-side:
- Always includes the current page's text (read from the DOM: .docs-prose /
  .api-doc), so on-page questions are answered from the page.
- Ranks the build-time llms-full.txt corpus against the question (saturating
  tf·idf + title boost, with a light stemmer so 'create a service' matches the
  'Services' page) and attaches the top pages — so off-page questions are
  answered too (Mintlify-style).
- 'Used N sources' now reflects the pages actually retrieved, not placeholders.

Pure client-side (one cached fetch of /llms-full.txt) — works in BYOK mode and
on pure-static hosting; degrades to current-page-only if llms-full.txt is
absent. Proxy context cap raised 8k→16k to fit the grounding context.

Known gap: llms-full.txt is docs-only, so API-reference operation bodies aren't
in the corpus yet (the current API page's DOM text is still included).
feat(ai): real retrieval — Ask AI answers from the docs
@oreofeolurin
oreofeolurin merged commit 4994bb2 into main Jun 11, 2026
1 check passed
@oreofeolurin
oreofeolurin deleted the feat/ai-composer-textarea-attachments branch June 11, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant